package com.xzy.mybatis.cipher.support.registry;

import com.xzy.mybatis.cipher.support.metadata.CryptoColumnMetadata;
import com.xzy.mybatis.cipher.support.metadata.CryptoTableMetadata;
import com.xzy.mybatis.cipher.support.strategy.crypto.CryptoStrategy;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author xzy.xiao
 * @since 2025/1/15  10:00
 */
@Slf4j
public class DataCryptoStrategyRegistry {

    private final CryptoMetadataRegistry cryptoMetadataRegistry;

    /**
     * {datasourceName:primaryCryptoStrategy}
     */
    private final Map<String, CryptoStrategy> DATASOURCE_2_PRIMARY_CRYPTO_STRATEGY_MAP = new ConcurrentHashMap<>(8);
    /**
     * {datasourceName:{tableName:{columnName:columnCryptoStrategy}}}
     */
    private final Map<String, Map<String, Map<String, CryptoStrategy>>> DATASOURCE_2_TABLE_2_COLUMN_CRYPTO_STRATEGY_MAP = new ConcurrentHashMap<>(8);

    public DataCryptoStrategyRegistry(CryptoMetadataRegistry cryptoMetadataRegistry) {
        this.cryptoMetadataRegistry = cryptoMetadataRegistry;
    }

    public void registerPrimaryStrategy(String datasourceName, CryptoStrategy cryptoStrategy) {
        log.debug("Register datasource primary data crypto strategy. datasourceName:{} cryptoStrategyName:{}", datasourceName, cryptoStrategy.getName());
        DATASOURCE_2_PRIMARY_CRYPTO_STRATEGY_MAP.put(datasourceName, cryptoStrategy);
    }

    public CryptoStrategy getPrimaryStrategy(String datasourceName) {
        return DATASOURCE_2_PRIMARY_CRYPTO_STRATEGY_MAP.getOrDefault(datasourceName, null);
    }

    public void registerStrategy(String datasourceName, String tableName, String columnName, CryptoStrategy cryptoStrategy) {
        log.debug("Register column data crypto strategy. datasourceName:{}, tableName:{}, columnName:{}, cryptoStrategyName:{}", datasourceName, tableName, columnName, cryptoStrategy.getName());
        DATASOURCE_2_TABLE_2_COLUMN_CRYPTO_STRATEGY_MAP
                .computeIfAbsent(datasourceName, k -> new ConcurrentHashMap<>())
                .computeIfAbsent(tableName, k -> new ConcurrentHashMap<>())
                .put(columnName, cryptoStrategy);
    }

    public CryptoStrategy getStrategy(String datasourceName, String tableName, String columnName) {

        CryptoStrategy cryptoStrategy = Optional.ofNullable(DATASOURCE_2_TABLE_2_COLUMN_CRYPTO_STRATEGY_MAP.get(datasourceName))
                .map(table2columnCryptoStrategyMap -> table2columnCryptoStrategyMap.get(tableName))
                .map(columnCryptoStrategyMap -> columnCryptoStrategyMap.get(columnName))
                .orElse(null);
        log.debug("The column '{}' in table '{}' use crypto strategy: {}", columnName, tableName, cryptoStrategy != null ? cryptoStrategy.getName() : "");

        return cryptoStrategy;
    }

    public CryptoStrategy getStrategy(Class<?> clazz, Field field) {

        CryptoColumnMetadata columnMetadata = cryptoMetadataRegistry.getColumnMetadata(clazz, field);
        if (null == columnMetadata) {
            return null;
        }

        CryptoTableMetadata tableMetadata = columnMetadata.getTableMetadata();
        CryptoStrategy cryptoStrategy = getStrategy(tableMetadata.getDatasourceName(), tableMetadata.getTableName(), columnMetadata.getColumnName());
        log.debug("The field '{}' in class '{}' use crypto strategy: {}", field.getName(), clazz.getName(), cryptoStrategy != null ? cryptoStrategy.getName() : "");

        return cryptoStrategy;
    }
}
